Skip to content

新增基于精英经验值的战后材料聚集#2406

Open
kaedelcb wants to merge 3 commits intobabalae:mainfrom
kaedelcb:main-EXP3
Open

新增基于精英经验值的战后材料聚集#2406
kaedelcb wants to merge 3 commits intobabalae:mainfrom
kaedelcb:main-EXP3

Conversation

@kaedelcb
Copy link
Contributor

@kaedelcb kaedelcb commented Oct 27, 2025

1、通过判断经验死亡的经验值判断是否进行材料聚集。
2、目前通过判断分辨率去调整匹配度,适配1080、2K和4K。
3、测试很久了,目前相当稳定。

Summary by CodeRabbit

发行说明

  • 新功能

    • 添加基于经验值的自动拾取逻辑:可根据经验图标检测结果决定是否拾取,并可在特定角色组合下启用。
    • 新增经验判定开关与参数,拾取流程在未检测到经验时可延后或跳过。
  • 用户界面

    • 更新拾取材料与脚本配置界面,加入“基于经验值”选项、拾取模式选择与数值阈值输入,并调整为四列布局以支持细粒度控制。

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

新增基于经验值的战利品拾取流程:添加经验识别对象与初始化方法,扩展配置与参数,调整战斗任务以在特定角色存在时异步检测经验掉落并在拾取时以检测结果为网关,同时更新若干 UI 布局与模型可见性。

Changes

内聚体 / 文件 变更摘要
经验识别与资源初始化
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs
新增公有字段 ExperienceRaRecognitionObject),新增私有字段 _gameScreenSize(游戏屏幕 RECT),并添加 InitializeRecognitionObject(int experience) 方法:根据屏幕宽度调整匹配阈值(基线 0.9,宽度 >1920/2560 时降至 0.6)、设置模板、ROI、掩码和绘制行为并调用 InitTemplate() 初始化。
战斗配置参数扩展
BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs
新增可观测属性 ExpKazuhaPickup(bool,默认 false)。
战斗参数更新
BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs
新增属性 ExpKazuhaPickup 并在构造与 SetDefault 中从配置赋值;移除构造中对 CheckBeforeBurst 的初始化赋值。
战斗流程与经验检测
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
添加静态状态 _isExperiencePickup、引入识别与配置引用;在检测到指定角色(万叶、琴)且启用 ExpKazuhaPickup 时启动异步 FindExp 识别任务;在战斗开始、拾取与完成流程中加入经验检测网关与等待延迟,新增 FindExp 循环识别并更新状态。
数据模型可见性调整
BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs
Avatars 属性从私有改为公有(public Avatar[] Avatars { get; set; })。
用户界面布局更新
BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml,
BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml
TaskSettingsPage:将聚集材料动作从 3 列扩展到 4 列,新增 ExpKazuhaPickup 切换并更新描述文本与分隔线样式;ScriptGroupConfigView:新增“只拾取精英掉落模式”和“拾取战斗人次阈值”区块,添加基于经验值的拾取控件与可见/可用绑定。

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Task as AutoFightTask
    participant Assets as AutoFightAssets
    participant Vision as 识别/视觉系统
    participant Loot as 战利品逻辑

    Task->>Task: 战斗开始前判断配置与队伍角色
    alt ExpKazuhaPickup 启用且角色存在
        Task->>Assets: InitializeRecognitionObject(experience)
        Assets-->>Task: 返回 ExperienceRa
        Task->>Vision: 启动 FindExp 异步识别循环
        Vision->>Assets: 使用 ExperienceRa 模板匹配
        Vision-->>Task: 更新 _isExperiencePickup (true/false)
    else 未启用或角色缺失
        Task->>Task: 跳过经验检测
    end

    Task->>Loot: 拾取阶段基于 _isExperiencePickup 决策
    alt _isExperiencePickup == true
        Loot->>Loot: 执行正常拾取
    else
        Loot->>Task: 延迟/跳过拾取
    end
Loading

Estimated code review effort

🎯 3 (中等) | ⏱️ ~20 分钟

需重点审查:

  • AutoFightTask.csFindExp 的异步循环、取消与状态同步与拾取流程的交互;
  • AutoFightAssets.cs 中基于屏幕宽度的阈值选择与 ROI/掩码、模板路径的正确性;
  • XAML 中新增绑定(ExpKazuhaPickup 等)与视图元素的 IsEnabled/IsChecked 绑定是否与 ViewModel / AutoFightConfig 一致;
  • CombatScenes.Avatar[] 公开可能引入的并发或外部修改问题。

Possibly related PRs

Poem

🐰 我在屏幕旁抖耳朵,找那经验小光斑,
万叶琴声作引导,阈值微调不慌乱,
异步巡查不停歇,战利品门等晴天,
小兔轻跳去摘取,代码里藏着欢颜。 ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 标题"新增基于精英经验值的战后材料聚集"清晰而具体地反映了这个拉取请求的主要变更。代码变更确实围绕添加一个新功能展开,即通过检测精英掉落的经验值来决定是否进行材料聚集,包括新增识别对象、配置参数、任务逻辑和用户界面。标题避免使用模糊术语,而是准确指出了功能的性质(基于经验值)和目标(战后材料聚集),使得维护人员扫描历史时能够快速理解核心改动。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs (1)

37-41: 将 Avatars 设为 public setter 破坏封装,存在运行时风险

外部可替换/置空 Avatars,可能导致:

  • AvatarCount 失真、索引越界(如 SelectAvatar/RefreshTeamAvatarIndexRectList 等依赖长度与索引)。
  • IndexRect 未赋值导致识别失败。

建议改为仅公开 getter,并保留 GetAvatars() 的只读视图:

-    public Avatar[] Avatars { set; get; } = [];
+    public Avatar[] Avatars { get; private set; } = [];
🧹 Nitpick comments (7)
BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs (1)

82-82: 为新开关补充注释与命名对齐(可选)

建议为 ExpKazuhaPickup 增补 XML 注释,说明与万叶/琴“聚集拾取”的关系;同时确认 UI 绑定字段命名与此属性完全一致,避免歧义。

BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (2)

47-52: ExperienceRa 单实例易被覆盖;_gameScreenSize 初始化时序有隐患(可改进)

  • InitializeRecognitionObject 会被多次调用生成不同经验模板,但类字段 ExperienceRa 每次都会被覆盖,后续调试显示/复用易混淆。建议移除该字段或改为 Dictionary<int, RecognitionObject> 缓存。
  • _gameScreenSize 直接取 SystemControl.GetGameScreenRect(TaskContext.Instance().GameHandle) 依赖初始化时序。更稳妥做法是使用注入的 systemInfo.GameScreenSize,保持与其余素材缩放逻辑一致。

274-298: InitializeRecognitionObject:建议使用 systemInfo 宽度、避免写全局字段并默认关闭绘制

当前实现功能可用,但可小幅优化以降低耦合和开销:

  • 使用 this.systemInfo.GameScreenSize.Width 判定阈值,避免依赖 _gameScreenSize。
  • 不写入类字段 ExperienceRa,直接返回新对象;或按经验值缓存。
  • DrawOnWindow 默认应为 false(仅调试时开启),减少遮罩层刷新。

可参考如下最小变更:

-    public RecognitionObject InitializeRecognitionObject(int experience)
+    public RecognitionObject InitializeRecognitionObject(int experience)
     {
-        var threshold = 0.9;
-        
-        if (_gameScreenSize.Width > 2560)
-        {
-            threshold =0.6;
-        }
-        else if (_gameScreenSize.Width > 1920)
-        {
-            threshold =0.6;
-        }
-        
-        ExperienceRa = new RecognitionObject
+        var width = this.systemInfo.GameScreenSize.Width;
+        var threshold = width > 1920 ? 0.6 : 0.9;
+
+        var ro = new RecognitionObject
         {
             Name = experience.ToString(),
             RecognitionType = RecognitionTypes.TemplateMatch,
-            TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFight", "experience_" + experience + ".png"),
+            TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFight", $"experience_{experience}.png", this.systemInfo),
             RegionOfInterest = new Rect((int)(CaptureRect.Width*0.145),(int)(CaptureRect.Height*0.5), (int)(CaptureRect.Width*0.02), (int)(CaptureRect.Height*0.22)),
             UseMask = true,
             Threshold = threshold,
-            DrawOnWindow = true,
+            DrawOnWindow = false,
         }.InitTemplate();
-        return  ExperienceRa;
+        return ro;
     }  
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (1)

27-35: 重复 using 可清理(可选)

BetterGenshinImpact.GameTask.AutoFight.Assets 被重复导入,建议删除重复 using,减少噪音。

BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml (2)

1428-1476: “聚集材料动作”新增列与开关布局可读性改进建议

  • Toggle 与说明 TextBlock 同格叠放(同列同 RowSpan)易出现遮挡/对齐偏差。建议在每个开关所在单元格内部使用垂直 StackPanel(上 Toggle、下 8pt 标签),更稳健。
  • IsEnabled 绑定一般应为 OneWay,避免 UI 状态回写 VM。当前对 KazuhaPickupEnabled 的 IsEnabled 采用 TwoWay 没必要。

建议修改如下:

- IsEnabled="{Binding PathingConfig.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}"
+ IsEnabled="{Binding PathingConfig.AutoFightConfig.KazuhaPickupEnabled}"  <!-- OneWay 默认 -->

同时可将每个 ToggleSwitch 与其说明 TextBlock 包装进一个 StackPanel 后再放入该列,避免同格叠放导致的可视/命中问题。


1442-1443: 说明文字统一与标点修正

两处页面对“基于经验/琴二次拾取”的描述略有出入,且混用英文逗号。建议统一文案并使用中文标点,便于本地化一致性。

- Text="(基于经验:通过精英经验值判断是否聚集 / 琴二次拾取:琴首次拾取空,再次拾取)"
+ Text="(基于经验:通过精英经验值判断是否聚集 / 琴二次拾取:首次拾取为空,再次尝试拾取)"
BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml (1)

1024-1094: “聚集材料动作”四列布局的小优化(绑定方向与布局)

  • 两个说明 TextBlock 与各自 Toggle 同格叠放,容易在不同 DPI/缩放下产生遮挡。建议在每个 Toggle 所在单元格内包一层垂直 StackPanel,Toggle 在上、8pt 说明在下。
  • IsEnabled 对 KazuhaPickupEnabled 应用 TwoWay 没必要,改 OneWay 更稳。

建议修改:

- IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}"
+ IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled}"

另外,建议将本页与 ScriptGroupConfigView.xaml 的说明文字保持一致,并统一中文标点:

- Text="(基于经验:检测精英经验值判断聚集 / 琴二次拾取:首次拾取空,再次拾取)"
+ Text="(基于经验:通过精英经验值判断是否聚集 / 琴二次拾取:首次拾取为空,再次尝试拾取)"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b43a73 and a96bca0.

⛔ Files ignored due to path filters (3)
  • BetterGenshinImpact/GameTask/AutoFight/Assets/1920x1080/experience_57.png is excluded by !**/*.png
  • BetterGenshinImpact/GameTask/AutoFight/Assets/1920x1080/experience_58.png is excluded by !**/*.png
  • BetterGenshinImpact/GameTask/AutoFight/Assets/1920x1080/experience_60.png is excluded by !**/*.png
📒 Files selected for processing (7)
  • BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (2 hunks)
  • BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs (1 hunks)
  • BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs (3 hunks)
  • BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (6 hunks)
  • BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs (1 hunks)
  • BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml (1 hunks)
  • BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs (1)
BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs (2)
  • Avatar (33-994)
  • Avatar (91-100)
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
BetterGenshinImpact/GameTask/SystemControl.cs (1)
  • SystemControl (11-343)
BetterGenshinImpact/GameTask/TaskContext.cs (3)
  • TaskContext (16-77)
  • TaskContext (24-26)
  • TaskContext (30-33)
BetterGenshinImpact/GameTask/GameTaskManager.cs (1)
  • GameTaskManager (29-193)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (4)
BetterGenshinImpact/GameTask/Common/TaskControl.cs (1)
  • TaskControl (15-226)
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
  • AutoFightAssets (10-299)
  • AutoFightAssets (54-57)
  • AutoFightAssets (59-62)
BetterGenshinImpact/GameTask/Common/NewRetry.cs (1)
  • NewRetry (16-200)
BetterGenshinImpact/View/Drawable/DrawContent.cs (1)
  • DrawContent (6-124)
🔇 Additional comments (4)
BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs (1)

53-58: ExpKazuhaPickup 从配置读取的一致性良好

在构造函数与 SetDefault 中均同步读取 ExpKazuhaPickup,保持了初始化路径一致性。LGTM。

Also applies to: 141-142

BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs (1)

160-161: 新增配置 ExpKazuhaPickup 默认值合理

公开可观察布尔量默认 false,符合“显式开启”预期。建议确认:

  • 配置文件序列化/反序列化已覆盖该字段(旧配置缺少该键时应安全回退为 false)。
  • 设置页的绑定与此属性一致,确保热更新生效。
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (1)

307-310: 仅在队伍包含“枫原万叶/琴”时开启经验检测 — 合理

Presence 检查简单直接,避免无谓的识别开销。LGTM。

BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml (1)

1022-1022: 分隔线结构化信息层级清晰

加入 Separator 且显式 BorderThickness 有助于分组清晰,赞。

Comment on lines +318 to 324
#region 基于战斗检测经验值开关万叶拾取功能同步任务

if (_taskParam.ExpKazuhaPickup && findExpAvatar) FindExp(cts2.Token);

#endregion

while (!cts2.Token.IsCancellationRequested)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

FindExp 调用未管理生命周期;绘制清理时机错误

  • FindExp 立即返回 CompletedTask,真正的检测在 Task.Run 内异步进行;同时 ClearAll 在外层 finally 中被过早调用,无法保证在任务结束后才清理绘制,可能导致闪烁或残留。
  • 建议让 FindExp 返回内部 Task,并将清理移入该任务的 finally;调用端可按需忽略返回值或在需要时等待其完成。

可按下方方式修改 FindExp(见对应方法处的完整 diff)。

🤖 Prompt for AI Agents
In BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs around lines 318 to
324, the call to FindExp starts a background Task internally and returns a
completed Task immediately while ClearAll is invoked from the outer finally too
early; change FindExp to return the internal Task so its lifecycle is managed by
the returned Task, move drawing cleanup (ClearAll) into the finally block inside
that internal Task, and update this call site to await or ignore the returned
Task as appropriate (e.g., if you don't need to wait, explicitly ignore the Task
to document intent).

Comment on lines +768 to +845
//基于万叶经验值判断是否拾取
private static Task FindExp(CancellationToken cts2)
{
var autoFightAssets = AutoFightAssets.Instance;

try
{
Task.Run(() =>
{
_isExperiencePickup = false;
var expLogo = false;

var experienceRas = new[]
{
autoFightAssets.InitializeRecognitionObject(60),
autoFightAssets.InitializeRecognitionObject(58),
autoFightAssets.InitializeRecognitionObject(57),
};

while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
{
try
{
cts2.ThrowIfCancellationRequested();

var result = NewRetry.WaitForAction(() =>
{
using (var ra = CaptureToRectArea())
{
_isExperiencePickup = experienceRas.Any(experienceRa =>
{
var isExist = ra.Find(experienceRa);
if (!isExist.IsExist())
{
return false;
}

var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要
expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;

return expLogo;
});
}
return _isExperiencePickup;
}, cts2, 1, 100).Result;
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"检测经验发生异常: {ex.Message}");
}
catch (Exception ex)
{
// Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
}

if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );

}

cts2.ThrowIfCancellationRequested();

}, cts2);
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"检测经验发生异常: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
}
finally
{
VisionContext.Instance().DrawContent.ClearAll();
}

return Task.CompletedTask;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

FindExp:将清理放入任务 finally,避免 .Result 阻塞并加入边界检查/色差容忍

  • 将外层 finally 的 ClearAll 移入 Task.Run 的 finally,使清理与任务生命周期一致;
  • 避免 .Result 阻塞,直接 await WaitForAction;
  • 采样点加入边界保护;
  • 颜色匹配加入微小容忍,降低分辨率/抗锯齿差异带来的误判。

建议变更:

-    private static Task FindExp(CancellationToken cts2)
-    {
-        var autoFightAssets = AutoFightAssets.Instance;
-        try  
-        {
-            Task.Run(() =>
-            {
-                _isExperiencePickup = false;
-                var expLogo = false;
-                
-                var experienceRas = new[]
-                {
-                   autoFightAssets.InitializeRecognitionObject(60), 
-                   autoFightAssets.InitializeRecognitionObject(58), 
-                   autoFightAssets.InitializeRecognitionObject(57),
-                };
-                
-                while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
-                {
-                    try
-                    {
-                        cts2.ThrowIfCancellationRequested();
-
-                        var result = NewRetry.WaitForAction(() =>
-                        {
-                            using (var ra = CaptureToRectArea())
-                            {
-                                _isExperiencePickup = experienceRas.Any(experienceRa => 
-                                {
-                                    var isExist = ra.Find(experienceRa);
-                                    if (!isExist.IsExist())
-                                    {
-                                        return false;
-                                    }
-                
-                                    var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要
-                                    expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;
-
-                                    return expLogo;
-                                });
-                            }
-                            return _isExperiencePickup;
-                        }, cts2, 1, 100).Result;
-                    }
-                    catch (OperationCanceledException ex)
-                    {
-                        Console.WriteLine($"检测经验发生异常: {ex.Message}");
-                    }
-                    catch (Exception ex)
-                    {
-                        // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-                    }
-                    
-                    if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );
-
-                }
-                
-                cts2.ThrowIfCancellationRequested();
-                
-            }, cts2); 
-        }
-        catch (OperationCanceledException ex)
-        {
-            Console.WriteLine($"检测经验发生异常: {ex.Message}");
-        }
-        catch (Exception ex)
-        {
-            Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-        }
-        finally
-        {
-            VisionContext.Instance().DrawContent.ClearAll();
-        }
-        
-        return Task.CompletedTask;
-    }
+    private static Task FindExp(CancellationToken token)
+    {
+        var assets = AutoFightAssets.Instance;
+        return Task.Run(async () =>
+        {
+            try
+            {
+                _isExperiencePickup = false;
+                var experienceRas = new[]
+                {
+                    assets.InitializeRecognitionObject(60),
+                    assets.InitializeRecognitionObject(58),
+                    assets.InitializeRecognitionObject(57),
+                };
+
+                while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested)
+                {
+                    try
+                    {
+                        token.ThrowIfCancellationRequested();
+                        var detected = await NewRetry.WaitForAction(() =>
+                        {
+                            using var ra = CaptureToRectArea();
+                            foreach (var ro in experienceRas)
+                            {
+                                var match = ra.Find(ro);
+                                if (!match.IsExist()) continue;
+                                var sampleX = match.X - 147;
+                                var sampleY = match.Y;
+                                if (sampleX < 0 || sampleY < 0 || sampleX >= ra.SrcMat.Cols || sampleY >= ra.SrcMat.Rows)
+                                {
+                                    continue;
+                                }
+                                var p = ra.SrcMat.At<Vec3b>(sampleY, sampleX);
+                                // 颜色近似(±3)以提升鲁棒性
+                                bool near = Math.Abs(p[0] - 253) <= 3 && Math.Abs(p[1] - 247) <= 3 && Math.Abs(p[2] - 172) <= 3;
+                                if (near) return (_isExperiencePickup = true);
+                            }
+                            return _isExperiencePickup;
+                        }, token, retryTimes: 1, delayMs: 100);
+
+                        if (detected)
+                        {
+                            Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用");
+                        }
+                    }
+                    catch (OperationCanceledException) { /* ignore */ }
+                    catch { /* ignore */ }
+                }
+            }
+            finally
+            {
+                VisionContext.Instance().DrawContent.ClearAll();
+            }
+        }, token);
+    }

如需进一步稳妥,可将 FightStatusFlag 也以 volatile/Interlocked 保护(参见上一条评论)。

🤖 Prompt for AI Agents
In BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs around lines 768 to
845: the review asks to move the cleanup into the spawned task’s finally, avoid
blocking .Result by awaiting WaitForAction, add bounds checks for the sample
coordinate (ensure X-147 >= 0 and Y within image height) and add a small
tolerance when comparing pixel RGB values to account for resolution/antialiasing
differences (e.g., compare absolute difference <= tolerance). Change the
synchronous Task-returning method to use async/await for the inner loop (await
NewRetry.WaitForAction(...)) and wrap the Task.Run delegate with try/finally
where VisionContext.Instance().DrawContent.ClearAll() is called, and ensure any
shared flags (like FightStatusFlag) are accessed in a thread-safe manner
(volatile or Interlocked) as needed.

Comment on lines +1515 to +1569
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="只拾取精英掉落模式"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="需要路径文件支持,需勾选区分怪物拾取才生效,可通过仓库标签查看,精英怪物可以在点位的更多》扩展配置》怪物标签中,可标记。只拾取标记为精英或传奇的点位。非精英允许自动拾取:战斗过程中掉落脚下的可以自动拾取,但不会执行万叶拾取和自动拾取配置逻辑。非精英关闭拾取:战斗过程中掉落到脚下的也不会自动拾取。"
TextWrapping="Wrap" />
<ComboBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="75"
SelectedValuePath="Key"
DisplayMemberPath="Value"
Margin="0,0,36,0"
ItemsSource="{Binding OnlyPickEliteDropsSource}"
SelectedValue="{Binding PathingConfig.AutoFightConfig.OnlyPickEliteDropsMode}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="拾取战斗人次阈值"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="拾取战斗人次阈值,当战斗人次小于一定次数,就结束战斗情况下,不触发拾取掉落物和万叶拾取配置,只有不小于2时才生效。"
TextWrapping="Wrap" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="120"
Margin="0,0,36,0"
Text="{Binding PathingConfig.AutoFightConfig.BattleThresholdForLoot}" />
</Grid>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

重复的配置区块需移除,避免混淆与不一致

这里新增了“只拾取精英掉落模式”和“拾取战斗人次阈值”两个 Grid,但文件中已分别存在相同功能区块(Lines 1490-1514 与 1570-1597)。重复会造成:

  • UI 冗余与用户困惑;
  • 属性双向绑定到同一字段时的聚焦/验证行为不一致;
  • MinWidth 不一致(75 vs 100)导致视觉不齐。

建议删除本段重复区块,保留前后已有的单一实现;若保留,请至少统一 MinWidth=100。

删除重复区块的示例补丁:

-    <Grid Margin="16">
-      <!-- 只拾取精英掉落模式(重复) -->
-      ...
-    </Grid>
-
-    <Grid Margin="16">
-      <!-- 拾取战斗人次阈值(重复) -->
-      ...
-    </Grid>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml around lines
1515 to 1569 there is a duplicate UI configuration block for "只拾取精英掉落模式" and
"拾取战斗人次阈值" that repeats functionality already present at lines ~1490-1514 and
~1570-1597; remove this duplicate block to avoid UI redundancy and binding
conflicts, or if you must keep it, ensure it matches the original implementation
exactly (use MinWidth=100 for consistency and keep the same
ItemsSource/SelectedValue/Text bindings and SelectedValuePath/DisplayMemberPath
settings) so bindings, focus/validation and visual alignment remain consistent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (3)

58-58: 跨线程访问 _isExperiencePickup 缺少内存可见性保证(竞态条件)

该标志在 FindExp 的后台任务中写入(第 777、797、806 行),在主流程中读取(第 496、507、513 行)。当前为普通静态字段,存在内存可见性风险,线程可能读取到旧值。

建议将其声明为 volatile 以确保跨线程内存可见性。

-    private static bool _isExperiencePickup = false;
+    private static volatile bool _isExperiencePickup = false;

318-322: FindExp 任务生命周期未管理,清理时机错误

FindExp 立即返回 CompletedTask,真正的检测在 Task.Run 内异步进行。同时 ClearAll 在 FindExp 方法的外层 finally 中被过早调用(第 839-842 行),无法保证在任务结束后才清理绘制内容。

建议让 FindExp 返回内部 Task,并将清理移入该任务的 finally 块中。调用端可根据需要忽略或等待返回的 Task。

参见第 768-845 行 FindExp 方法的详细修改建议。


768-845: FindExp 方法存在多个未解决的关键问题

该方法存在以下问题(与先前评审意见一致):

  1. 生命周期管理错误:返回 Task.CompletedTask 而实际检测在 Task.Run 内部异步执行,导致调用方无法追踪任务状态
  2. 阻塞调用:第 812 行使用 .Result 阻塞等待,应改用 await
  3. 清理时机错误:第 839-842 行的 ClearAll 在外层 finally 中执行,会在后台任务完成前被调用
  4. 缺少边界检查:第 805 行 isExist.X - 147 可能导致负数或越界访问
  5. 颜色匹配过于严格:第 806 行使用精确 RGB 匹配,不同分辨率/抗锯齿下容易误判

建议的修复方案(来自先前评审):

-    private static Task FindExp(CancellationToken cts2)
+    private static Task FindExp(CancellationToken token)
     {
         var autoFightAssets = AutoFightAssets.Instance;
-        try  
-        {
-            Task.Run(() =>
+        return Task.Run(async () =>
+        {
+            try
             {
                 _isExperiencePickup = false;
-                var expLogo = false;
                 
                 var experienceRas = new[]
                 {
                    autoFightAssets.InitializeRecognitionObject(60), 
                    autoFightAssets.InitializeRecognitionObject(58), 
                    autoFightAssets.InitializeRecognitionObject(57),
                 };
                 
-                while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
+                while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested)
                 {
                     try
                     {
-                        cts2.ThrowIfCancellationRequested();
-
-                        var result = NewRetry.WaitForAction(() =>
+                        token.ThrowIfCancellationRequested();
+                        await NewRetry.WaitForAction(() =>
                         {
                             using (var ra = CaptureToRectArea())
                             {
-                                _isExperiencePickup = experienceRas.Any(experienceRa => 
+                                foreach (var experienceRa in experienceRas)
                                 {
                                     var isExist = ra.Find(experienceRa);
                                     if (!isExist.IsExist())
                                     {
-                                        return false;
+                                        continue;
                                     }
-                
-                                    var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147);
-                                    expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;
-
-                                    return expLogo;
-                                });
+                                    
+                                    var sampleX = isExist.X - 147;
+                                    var sampleY = isExist.Y;
+                                    
+                                    // 边界检查
+                                    if (sampleX < 0 || sampleY < 0 || 
+                                        sampleX >= ra.SrcMat.Cols || sampleY >= ra.SrcMat.Rows)
+                                    {
+                                        continue;
+                                    }
+                                    
+                                    var p = ra.SrcMat.At<Vec3b>(sampleY, sampleX);
+                                    // 颜色容差匹配(±3)提升鲁棒性
+                                    bool colorMatch = Math.Abs(p[0] - 253) <= 3 && 
+                                                     Math.Abs(p[1] - 247) <= 3 && 
+                                                     Math.Abs(p[2] - 172) <= 3;
+                                    
+                                    if (colorMatch)
+                                    {
+                                        _isExperiencePickup = true;
+                                        break;
+                                    }
+                                }
                             }
                             return _isExperiencePickup;
-                        }, cts2, 1, 100).Result;
+                        }, token, 1, 100);
+                        
+                        if (_isExperiencePickup)
+                        {
+                            Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用");
+                        }
                     }
-                    catch (OperationCanceledException ex)
-                    {
-                        Console.WriteLine($"检测经验发生异常: {ex.Message}");
-                    }
-                    catch (Exception ex)
-                    {
-                        // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-                    }
-                    
-                    if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );
-
+                    catch (OperationCanceledException) { /* 正常取消,忽略 */ }
+                    catch { /* 检测异常,继续循环 */ }
                 }
-                
-                cts2.ThrowIfCancellationRequested();
-                
-            }, cts2); 
-        }
-        catch (OperationCanceledException ex)
-        {
-            Console.WriteLine($"检测经验发生异常: {ex.Message}");
-        }
-        catch (Exception ex)
-        {
-            Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-        }
-        finally
-        {
-            VisionContext.Instance().DrawContent.ClearAll();
-        }
-        
-        return Task.CompletedTask;
+            }
+            finally
+            {
+                VisionContext.Instance().DrawContent.ClearAll();
+            }
+        }, token);
     }
🧹 Nitpick comments (3)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (3)

26-26: 删除重复的 using 声明

第 26 行和第 34 行都导入了 BetterGenshinImpact.GameTask.AutoFight.Assets,存在重复。

 using BetterGenshinImpact.GameTask.AutoFight.Assets;
 using BetterGenshinImpact.View.Drawable;
 using BetterGenshinImpact.Core.Recognition.OpenCv;
 using BetterGenshinImpact.GameTask.Common.BgiVision;
 using Vanara.PInvoke;
 using BetterGenshinImpact.GameTask.AutoPathing.Handler;
 using BetterGenshinImpact.GameTask.AutoPick.Assets;
 using BetterGenshinImpact.GameTask.AutoPathing.Model;
-using BetterGenshinImpact.GameTask.AutoFight.Assets;

Also applies to: 34-34


496-500: 优化延迟条件逻辑

_isExperiencePickup 为 false 时添加延迟等待经验显示的逻辑是合理的,但建议明确日志输出的含义。当前代码在所有满足条件的情况下都会延迟 1 秒,可能影响已检测到经验的情况。

建议确认此延迟仅在确实需要等待经验值显示时执行。


507-510: 复杂的布尔条件可考虑添加注释

第 507 行的条件逻辑正确,但较为复杂。建议添加注释说明何时跳过拾取:

  • 战斗人次低于阈值,且(未启用经验值判断 或 未检测到经验值)时跳过。
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a96bca0 and d613f92.

📒 Files selected for processing (1)
  • BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (4)
BetterGenshinImpact/GameTask/Common/TaskControl.cs (1)
  • TaskControl (15-226)
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
  • AutoFightAssets (10-299)
  • AutoFightAssets (54-57)
  • AutoFightAssets (59-62)
BetterGenshinImpact/GameTask/Common/NewRetry.cs (1)
  • NewRetry (16-200)
BetterGenshinImpact/View/Drawable/DrawContent.cs (1)
  • DrawContent (6-124)
🔇 Additional comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)

307-309: 逻辑正确,实现清晰

通过 HashSet 检查队伍中是否存在拾取功能角色(枫原万叶或琴)的逻辑正确且高效。


515-515: 拾取门控逻辑正确

基于经验值检测结果的拾取门控条件实现正确,符合需求。

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)

318-322: FindExp 调用未管理生命周期;请改为返回内部 Task 并在调用处显式持有/忽略

当前 FindExp(cts2.Token) 内部再 Task.Run 且方法本身返回 Task.CompletedTask,调用方无法管理其生命周期;外层清理也可能过早执行。建议让 FindExp 直接返回内部任务,并在此处显式持有(或用 _ = 明确忽略)。

-                if (_taskParam.ExpKazuhaPickup && findExpAvatar) FindExp(cts2.Token);
+                if (_taskParam.ExpKazuhaPickup && findExpAvatar)
+                {
+                    // 显式启动并忽略返回的 Task,表明有意异步执行且不等待
+                    _ = FindExp(cts2.Token);
+                }

768-845: FindExp 存在多处并发与正确性隐患:返回值、清理时机、.Result 阻塞、像素越界与颜色容忍度

  • 方法返回 CompletedTask,而真实工作在内部 Task,调用方无法管理其生命周期;外层 finally 的 ClearAll() 可能比内部任务先执行,导致闪烁/残留。
  • 使用 NewRetry.WaitForAction(...).Result 在线程池线程上同步阻塞,易造成线程饥饿与不可控延迟。
  • 采样点 isExist.X - 147 未做边界检查,可能越界访问 SrcMat
  • 使用严格 RGB 等值判断,易受分辨率/抗锯齿影响误判。
  • 循环条件读取 FightStatusFlag 为普通属性(非 volatile/Interlocked),跨线程可见性无保证。

建议重构如下:

  • FindExp 返回内部 Task,并将 ClearAll() 放入其 finally
  • await 调用 WaitForAction,避免 .Result
  • 对采样坐标做边界检查。
  • 颜色判断加入微小容忍度(如 ±3)。
  • FightStatusFlag 增加内存可见性保证(见下方附加 diff)。
-    private static Task FindExp(CancellationToken cts2)
-    {
-        var autoFightAssets = AutoFightAssets.Instance;
-
-        try  
-        {
-            Task.Run(() =>
-            {
-                _isExperiencePickup = false;
-                var expLogo = false;
-                
-                var experienceRas = new[]
-                {
-                   autoFightAssets.InitializeRecognitionObject(60), 
-                   autoFightAssets.InitializeRecognitionObject(58), 
-                   autoFightAssets.InitializeRecognitionObject(57),
-                };
-                
-                while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
-                {
-                    try
-                    {
-                        cts2.ThrowIfCancellationRequested();
-
-                        var result = NewRetry.WaitForAction(() =>
-                        {
-                            using (var ra = CaptureToRectArea())
-                            {
-                                _isExperiencePickup = experienceRas.Any(experienceRa => 
-                                {
-                                    var isExist = ra.Find(experienceRa);
-                                    if (!isExist.IsExist())
-                                    {
-                                        return false;
-                                    }
-                
-                                    var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要
-                                    expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;
-
-                                    return expLogo;
-                                });
-                            }
-                            return _isExperiencePickup;
-                        }, cts2, 1, 100).Result;
-                    }
-                    catch (OperationCanceledException ex)
-                    {
-                        Console.WriteLine($"检测经验发生异常: {ex.Message}");
-                    }
-                    catch (Exception ex)
-                    {
-                        // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-                    }
-                    
-                    if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );
-
-                }
-                
-                cts2.ThrowIfCancellationRequested();
-                
-            }, cts2); 
-        }
-        catch (OperationCanceledException ex)
-        {
-            Console.WriteLine($"检测经验发生异常: {ex.Message}");
-        }
-        catch (Exception ex)
-        {
-            Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-        }
-        finally
-        {
-            VisionContext.Instance().DrawContent.ClearAll();
-        }
-        
-        return Task.CompletedTask;
-    }
+    private static Task FindExp(CancellationToken token)
+    {
+        var assets = AutoFightAssets.Instance;
+        return Task.Run(async () =>
+        {
+            try
+            {
+                _isExperiencePickup = false;
+                var experienceRas = new[]
+                {
+                    assets.InitializeRecognitionObject(60),
+                    assets.InitializeRecognitionObject(58),
+                    assets.InitializeRecognitionObject(57),
+                };
+
+                while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested)
+                {
+                    try
+                    {
+                        token.ThrowIfCancellationRequested();
+                        var detected = await NewRetry.WaitForAction(() =>
+                        {
+                            using var ra = CaptureToRectArea();
+                            foreach (var ro in experienceRas)
+                            {
+                                var match = ra.Find(ro);
+                                if (!match.IsExist()) continue;
+                                var sx = match.X - 147;
+                                var sy = match.Y;
+                                if (sx < 0 || sy < 0 || sx >= ra.SrcMat.Cols || sy >= ra.SrcMat.Rows) continue;
+                                var p = ra.SrcMat.At<Vec3b>(sy, sx);
+                                const int tol = 3;
+                                bool near =
+                                    Math.Abs(p[0] - 253) <= tol &&
+                                    Math.Abs(p[1] - 247) <= tol &&
+                                    Math.Abs(p[2] - 172) <= tol;
+                                if (near) return (_isExperiencePickup = true);
+                            }
+                            return _isExperiencePickup;
+                        }, token, retryTimes: 1, delayMs: 100);
+
+                        if (detected)
+                        {
+                            Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用");
+                        }
+                    }
+                    catch (OperationCanceledException) { /* ignore */ }
+                    catch { /* ignore */ }
+                }
+            }
+            finally
+            {
+                VisionContext.Instance().DrawContent.ClearAll();
+            }
+        }, token);
+    }

附:为 FightStatusFlag 增加可见性保证(在属性定义处修改)。

-public static bool FightStatusFlag { get; set; } = false;
+private static bool _fightStatusFlag;
+public static bool FightStatusFlag
+{
+    get => Volatile.Read(ref _fightStatusFlag);
+    set => Volatile.Write(ref _fightStatusFlag, value);
+}
🧹 Nitpick comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)

27-35: 移除重复 using 以消除冗余

using BetterGenshinImpact.GameTask.AutoFight.Assets; 出现两次(行 27 与行 34)。保留一次即可。

-using BetterGenshinImpact.GameTask.AutoFight.Assets;

496-501: 等待经验显示时使用局部取消令牌,避免外部取消不一致

此处建议用 cts2.Token 保持与本次战斗生命周期一致;并建议在日志后快速返回而非固定 1s 等待(可由 WaitForAction 驱动)。

-                    await Delay(1000, ct);
+                    await Delay(1000, cts2.Token);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d613f92 and 3f2b06d.

📒 Files selected for processing (1)
  • BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (4)
BetterGenshinImpact/GameTask/Common/TaskControl.cs (1)
  • TaskControl (15-226)
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
  • AutoFightAssets (10-299)
  • AutoFightAssets (54-57)
  • AutoFightAssets (59-62)
BetterGenshinImpact/GameTask/Common/NewRetry.cs (1)
  • NewRetry (16-200)
BetterGenshinImpact/View/Drawable/DrawContent.cs (1)
  • DrawContent (6-124)
🔇 Additional comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)

58-58: 已采用 volatile 保证跨线程可见性 — LGTM

_isExperiencePickup 使用 volatile 恰当,解决了读写可见性问题。


507-516: 语义确认:经验命中时绕过“战斗人次”门槛

当前条件使得“经验识别命中”会放行拾取,即使 countFight < BattleThresholdForLoot。确认这是期望行为吗?若希望两者都满足,可改为 AND。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant